﻿using Entidades;
using RandomKeyGenerator;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace AlgoritmoConstrutivo.entidades
{
    [Serializable]
    public class Cluster : IComparable
    {
        //Id do Cluster
        private int id;

        private List<int> nos;
       
        //Depois de criar um Cluster esta variavel estatica eh atualizada com o seu ID
        private static int ultimoId = -1;

        //Quantidade de arestas internas contidas no Cluster
        private Decimal internas;

        //Quantidade de arestas externas contidas no Cluster
        private Decimal externas;

        //Modularization Factor deste Cluster (internas/internas + externas/2)
        private Decimal mf;

        /// <summary>
        /// Construtor. Ao se criar um novo Cluster, um novo ID eh criado e a variavel estatica de ultimoId eh atualizada.
        /// </summary>
        public Cluster(bool createNewId)
        {
           
                if (createNewId)
                    this.id = ++Cluster.ultimoId;
                else
                    this.id = -1;
            
        }

    
        public int CompareTo(object obj)
        {
            Cluster key1 = obj as Cluster;

            if (key1.nos.Count > this.nos.Count)
                return 1;

            else if (key1.nos.Count == this.nos.Count)
                return 0;
            else
                return -1;
        }

        /// <summary>
        /// Deleta todos os Cluster ja criados.
        /// </summary>
        public static void ZeraNumClusters()
        {
            Cluster.ultimoId = -1;
        }

        public Decimal CalculaMFGrafoPonderado(Grafo software)
        {
            this.internas = 0;
            this.externas = 0;

            for (int i = 0; i < this.nos.Count; i++) //para todos os nós desse cluster....
            {
                Vizinhos arestasSaindo = software.RetornaNo(this.nos[i]).ArestasQueSaem; //pegam-se os vizinhos
                Vizinhos arestasChegando = software.RetornaNo(this.nos[i]).ArestasQueEntram; //pegam-se os vizinhos

                for (int k = 0; k < arestasSaindo.Count; k++)
                {
                    if (arestasSaindo[k].Cluster == null || arestasSaindo[k].Cluster.id != this.id) //dúvida conceitual. Se um módulo ainda não tem definido um cluster, posso assumir que uma referencia para ele é uma externa??
                    {
                        //this.externas++; //não estão no mesmo cluster. Esse cluster ganha uma nova externa.

                        this.externas += arestasSaindo.Pesos[k];

                    }
                    else
                    {
                        this.internas += arestasSaindo.Pesos[k]; //os ids são iguais. Eles estão no mesmo cluster.
                    }
                }

                for (int l = 0; l < arestasChegando.Count; l++)
                {
                    if (arestasChegando[l].Cluster == null || arestasChegando[l].Cluster.id != this.id)
                        this.externas += arestasChegando.Pesos[l]; //não estão no mesmo cluster. Esse cluster ganha uma nova externa.
                    else
                        continue; //já contei estas arestas no primeiro for.
                }
            }

            if (this.internas == 0 && this.externas == 0)
                return 0;

            this.mf = Math.Round((this.internas / (this.internas + this.externas / 2)), 5);



            return this.mf;
        }

        public Decimal CalculaMF(Grafo software)
        {
            this.internas = 0;
            this.externas = 0;

            for (int i = 0; i < this.nos.Count; i++) //para todos os nós desse cluster....
            {
                Vizinhos arestasSaindo = software.RetornaNo(this.nos[i]).ArestasQueSaem; //pegam-se os vizinhos
                Vizinhos arestasChegando = software.RetornaNo(this.nos[i]).ArestasQueEntram; //pegam-se os vizinhos

                foreach (No n in arestasSaindo)
                {
                    if (n.Cluster == null || n.Cluster.id != this.id) //dúvida conceitual. Se um módulo ainda não tem definido um cluster, posso assumir que uma referencia para ele é uma externa??
                    {
                        this.externas++; //não estão no mesmo cluster. Esse cluster ganha uma nova externa.
                    }
                    else
                    {
                        this.internas++; //os ids são iguais. Eles estão no mesmo cluster.
                    }
                }

                foreach (No n in arestasChegando)
                {
                    if (n.Cluster == null || n.Cluster.id != this.id)
                        this.externas++; //não estão no mesmo cluster. Esse cluster ganha uma nova externa.
                    else
                        continue; //já contei estas arestas no primeiro for.
                }
            }

            if (this.internas == 0 && this.externas == 0)
                return 0;
           
            this.mf = Math.Round((this.internas / (this.internas + this.externas / 2)), 5);

            

            return this.mf;
        }

    

        /// <summary>
        /// Verificar se ao retirar um novo nó deste cluster, o MF aumenta, diminui ou fica igual.
        /// </summary>
        /// <param name="software">Grafo representando o software a ser trabalhado</param>
        /// <param name="idNo">Id do nó que pode vir a ser retirado</param>
        /// <returns>True se o MF for melhorado, ou false se não</returns>
        public bool AvaliaRetiradaDeUmNo(Grafo software, int idNo, out Decimal mfRef, int clusterDestino)
        {
             Decimal arestasSaindoNoMesmoCluster = 0;
             Decimal arestasSaindoIndoParaOutroCluster = 0;


             Decimal arestasChegandoNoMesmoCluster = 0;
             Decimal arestasChegandoDeOutrosCluster = 0;

            if (!software.Ponderado)
            {
                for (int i = 0; i < software.RetornaNo(idNo).ArestasQueSaem.Count; i++) //todas as arestas que saem deste nó.
                {
                    if (software.RetornaNo(idNo).ArestasQueSaem[i].Cluster.id == this.id) //se a aresta que sai está no mesmo cluster
                        arestasSaindoNoMesmoCluster++; //ela era interna e passa a ser externa
                    else //as arestas saindo, estão indo para outros clusters (mesmo do destino ou nao)
                        arestasSaindoIndoParaOutroCluster++; //ela era externa e deixa de ser.
                }

                for (int i = 0; i < software.RetornaNo(idNo).ArestasQueEntram.Count; i++) //todas as arestas que saem deste nó.
                {
                    if (software.RetornaNo(idNo).ArestasQueEntram[i].Cluster.id == this.id) //se a aresta que sai está no mesmo cluster
                        arestasChegandoNoMesmoCluster++; //ela era interna e passa a ser externa
                    else
                        arestasChegandoDeOutrosCluster++; //ela era externa e deixa de ser
                }
            }

            else
            {
                for (int i = 0; i < software.RetornaNo(idNo).ArestasQueSaem.Count; i++) //todas as arestas que saem deste nó.
                {

                    Cluster cl = software.RetornaNo(idNo).ArestasQueSaem[i].Cluster;
                    int peso = software.RetornaNo(idNo).ArestasQueSaem.Pesos[i];

                    if (cl.id == this.id) //se a aresta que sai está no mesmo cluster
                        arestasSaindoNoMesmoCluster+=peso; //ela era interna e passa a ser externa

                    else //as arestas saindo, estão indo para outros clusters (mesmo do destino ou nao)
                        arestasSaindoIndoParaOutroCluster+=peso; //ela era externa e deixa de ser.
                }

                for (int i = 0; i < software.RetornaNo(idNo).ArestasQueEntram.Count; i++) //todas as arestas que saem deste nó.
                {
                    Cluster cl = software.RetornaNo(idNo).ArestasQueEntram[i].Cluster;
                    int peso = software.RetornaNo(idNo).ArestasQueEntram.Pesos[i];

                    if (software.RetornaNo(idNo).ArestasQueEntram[i].Cluster.id == this.id) //se a aresta que sai está no mesmo cluster
                        arestasChegandoNoMesmoCluster+=peso; //ela era interna e passa a ser externa
                    else
                        arestasChegandoDeOutrosCluster+=peso; //ela era externa e deixa de ser
                }
            }

            Decimal novasExternas = arestasSaindoNoMesmoCluster + arestasChegandoNoMesmoCluster;
            Decimal externasQueNaoExistemMais = arestasSaindoIndoParaOutroCluster + arestasChegandoDeOutrosCluster;

            //checar se ficou < 0

            Decimal novoMF = 0;

            if (this.internas - novasExternas != 0)
                novoMF = (this.internas - novasExternas) / ((this.internas - novasExternas) + ((this.externas + novasExternas - externasQueNaoExistemMais) / 2));

            if (novoMF < 0)
                throw new ArgumentException("Um MF não pode ser menor do que 0");
            else if (Double.IsNaN((Double)novoMF))
                novoMF = 0;

            mfRef = novoMF;

            if (novoMF > this.mf)
                return true;
            return false;
        }


         /// <summary>
        /// Verificar se ao inserir um novo nó neste cluster, o MF aumenta, diminui ou fica igual.
        /// </summary>
        /// <param name="software">Grafo representando o software a ser trabalhado</param>
        /// <param name="idNo">Id do nó que pode vir a fazer parte deste cluster</param>
        /// <returns>True se o MF for melhorado, ou false se não</returns>
        public bool AvaliaInsercaoDeUmNo(Grafo software, int idNo)
        {
            Decimal a;
            return this.AvaliaInsercaoDeUmNo(software, idNo, out a);
        }

        public int RetornaIdPrimeiroNo()
        {
            if (this.nos.Count == 0)
                throw new Exception("O grafo em questão não possui nenhum nó");

            return this.nos[0];
        }


        /// <summary>
        /// Verificar se ao inserir um novo nó neste cluster, o MF aumenta, diminui ou fica igual.
        /// </summary>
        /// <param name="software">Grafo representando o software a ser trabalhado</param>
        /// <param name="idNo">Id do nó que pode vir a fazer parte deste cluster</param>
        /// <returns>True se o MF for melhorado, ou false se não</returns>
        public bool AvaliaInsercaoDeUmNo(Grafo software, int idNo, out Decimal mfRef)
        {
            Decimal nosQueSaemIndoClusterDestino = 0;
            Decimal nosQueSaemIndoOutrosClusters = 0;

            Decimal nosQueChegamVindoClusterDestino = 0;
            Decimal nosQueChegamVindoOutrosClusters = 0;


            int count = software.RetornaNo(idNo).ArestasQueSaem.Count;

            No no = software.RetornaNo(idNo);

            if (!software.Ponderado)
            {
                for (int idArestaSai = 0; idArestaSai < count; idArestaSai++) //todas as arestas que saem deste nó.
                {
                    Cluster cl = no.ArestasQueSaem[idArestaSai].Cluster;

                    //se a aresta de id idaresta saindo deste nó a ser avaliado tiver destino neste cluster, este ganha uma nova interna, se não ganha uma nova externa.
                    if (cl != null && cl.id == this.id)
                        nosQueSaemIndoClusterDestino++;  //novas internas para este cluster
                    else
                        nosQueSaemIndoOutrosClusters++; //novas externas para este clusters
                }

                count = software.RetornaNo(idNo).ArestasQueEntram.Count;

                for (int idArestaEntra = 0; idArestaEntra < count; idArestaEntra++) //todas as arestas que chegam neste nó
                {
                    Cluster cl = no.ArestasQueEntram[idArestaEntra].Cluster;
                    //se a aresta que entra neste nó a ser avaliado tem origem neste cluster, este ganha uma nova interna, se não ganha uma nova externa.
                    //TODO DUVIDA CONCEITUAL. 
                    if (cl != null && cl.id == this.id)
                        nosQueChegamVindoClusterDestino++; //novas internas para este cluster
                    else
                        nosQueChegamVindoOutrosClusters++; //novas externas para este clusters
                }
            }


            else
            {
                for (int idArestaSai = 0; idArestaSai < count; idArestaSai++) //todas as arestas que saem deste nó.
                {
                    Cluster cl = no.ArestasQueSaem[idArestaSai].Cluster;
                    int peso = no.ArestasQueSaem.Pesos[idArestaSai];

                    //se a aresta de id idaresta saindo deste nó a ser avaliado tiver destino neste cluster, este ganha uma nova interna, se não ganha uma nova externa.
                    if (cl != null && cl.id == this.id)
                        nosQueSaemIndoClusterDestino+=peso;  //novas internas para este cluster
                    else
                        nosQueSaemIndoOutrosClusters+=peso; //novas externas para este clusters
                }

                count = software.RetornaNo(idNo).ArestasQueEntram.Count;

                for (int idArestaEntra = 0; idArestaEntra < count; idArestaEntra++) //todas as arestas que chegam neste nó
                {
                    Cluster cl = no.ArestasQueEntram[idArestaEntra].Cluster;
                    int peso = no.ArestasQueEntram.Pesos[idArestaEntra];


                    //se a aresta que entra neste nó a ser avaliado tem origem neste cluster, este ganha uma nova interna, se não ganha uma nova externa.
                    //TODO DUVIDA CONCEITUAL. 
                    if (cl != null && cl.id == this.id)
                        nosQueChegamVindoClusterDestino+=peso; //novas internas para este cluster
                    else
                        nosQueChegamVindoOutrosClusters+=peso; //novas externas para este clusters
                }
            }

                Decimal novasInternas = nosQueSaemIndoClusterDestino + nosQueChegamVindoClusterDestino;
                Decimal novasExternas = nosQueSaemIndoOutrosClusters + nosQueChegamVindoOutrosClusters;
                Decimal externasQueDeixamDeExistir = novasInternas;



                Decimal novoMF = (this.internas + novasInternas) / ((this.internas + novasInternas) + ((this.externas + novasExternas - externasQueDeixamDeExistir) / 2));
            

            if (novoMF < 0)
                throw new ArgumentException("Um MF não pode ser menor do que 0");

            else if (Double.IsNaN((Double)novoMF)) //TODO porque isto está aqui?
                novoMF = 0;

            mfRef = novoMF;

            if (novoMF > this.mf)
                return true;

            return false;
        }



        /// <summary>
        /// Responsável por definir o número de clusters (NUM_CLUSTER) que uma geração irá trabalhar. Este algoritmo escolhe um número entre 20% e 40% do número de módulos de uma
        /// instância.
        /// </summary>
        /// <param name="software"></param>
        /// <param name="randomKey"></param>
        /// <returns></returns>
        public static int GerarNumeroClusters(bool randomKey,int numNos, Configuration2 cfg)
        {
           // double min = (Configuration.MIN_CLUSTERS * numNos);
            //double max = (Configuration.MAX_CLUSTERS * numNos);

            double min = (cfg.MinClusters * numNos);
            double max = (cfg.MaxClusters * numNos);


            if (!randomKey)
                return RandomNumberGenerator.GenerateRandomNumberFromInterval((int)min, (int)max);
            else
            {
                Double numAleatorio = RandomNumberGenerator.GenerateRandomNumber();

                numAleatorio = numAleatorio * (max - min);

                return (int)Math.Ceiling(numAleatorio);
            }
        }


        public override bool Equals(object obj)
        {
            return (obj as Cluster).id == this.id;
        }


        public Decimal Mf
        {
            get { return mf; }
            set { mf = value; }
        }

        public int Id
        {
            get { return id; }
            set { id = value; }
        }
        

        public Decimal Internas
        {
            get { return internas; }
            set { internas = value; }
        }

        public void InicializarNos()
        {
           

            if (this.nos != null)
                throw new ArgumentException("A lista de nós do cluster " + this.id + " já está inicializada");

           this.nos = new List<int>();
        }

        public void RemoverNo(int id)
        {
           
                if (!this.nos.Contains(id))
                    throw new ArgumentException("Tentou-se remover o nó de identificador " + id + " que não já existe no cluster + " + this.id);

                this.nos.Remove(id);
                
        }

        public int RetornarNumNosCluster()
        {
            if (this.nos == null)
                return 0;

            return this.nos.Count;
        }

        public int RetornarNo(int id)
        {
            if (this.nos == null || this.nos.Count == 0 || id >= this.nos.Count)
                return -1;

            return this.nos[id];
              
        }


        public bool PossuiNo(int id)
        {
            if (this.nos == null)
            {
                this.InicializarNos();
                return false;
            }

            else if (this.nos == null || this.nos.Count == 0)
                return false;

            else if (this.nos.Contains(id))
                return true;

            return false;
        }

        internal void AdicionarNo(int id, Grafo software)
        {
          

                if (this.nos == null)
                    this.InicializarNos();
            

            if (this.nos.Contains(id))
                throw new ArgumentException("Tentou-se adicionar o nó de identificador " + id + " que já existe no cluster + " + this.id);

            if (software.RetornaNo(id).Cluster == null)
                throw new ArgumentException("Tentou-se adicionar o nó de identificador " + id + " no cluster " + this.id +" mas esse nó não tem um cluster configurado");


            this.nos.Add(id);
         
        }

     
        public Decimal Externas
        {
            get { return externas; }
            set { externas = value; }
        }

        public override string ToString()
        {
  
            return (" O cluster de ID" + this.id + " possui " + this.nos.Count +" nós e possui um MF de " + this.mf + ". Possui " + this.internas + " arestas internas e possui "+ this.externas + " externas");
        }

    }
}
